
#include <BStyle>
#include <BFont>
#include <BImage>
#include <BReadWrite>
#include <BApplication>
#include <BSystem>

#include <BFileDialog>
#include "member_BFileDialog.h"
#include "IGlobal.h"

using namespace BWE;

#define member					(*(member_BFileDialog*)_ptr)
#define member_allocate()		_ptr = new member_BFileDialog(this)
#define member_release()		delete (member_BFileDialog*)_ptr

BFileDialog* GetFileDialog()
{
	static BHolder<BFileDialog> fileDialog = new BFileDialog();
	fileDialog->setModal(true);
	return fileDialog;
}

const BString& BFileDialog::OpenEntry(BWidget* parent, const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openEntry(title, dir);
}
const BString& BFileDialog::OpenEntry(const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openEntry(title, dir);
}
const BStringList& BFileDialog::OpenEntries(BWidget* parent, const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openEntries(title, dir);
}
const BStringList& BFileDialog::OpenEntries(const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openEntries(title, dir);
}

const BString& BFileDialog::OpenFolder(BWidget* parent, const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openFolder(title, dir);
}
const BString& BFileDialog::OpenFolder(const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openFolder(title, dir);
}
const BStringList& BFileDialog::OpenFolders(BWidget* parent, const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openFolders(title, dir);
}
const BStringList& BFileDialog::OpenFolders(const BString& title, const BString& dir)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openFolders(title, dir);
}

const BString& BFileDialog::OpenFile(BWidget* parent, const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openFile(title, dir, filter);
}
const BString& BFileDialog::OpenFile(const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openFile(title, dir, filter);
}
const BStringList& BFileDialog::OpenFiles(BWidget* parent, const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->openFiles(title, dir, filter);
}
const BStringList& BFileDialog::OpenFiles(const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->openFiles(title, dir, filter);
}

const BString& BFileDialog::SaveFile(BWidget* parent, const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setParent(parent);
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	return fileDialog->saveFile(title, dir, filter);
}
const BString& BFileDialog::SaveFile(const BString& title, const BString& dir, const BString& filter)
{
	BFileDialog* fileDialog = GetFileDialog();
	fileDialog->setIcon(BSystem::EntryIcon(dir));
	fileDialog->setParent(BApplication::activeWindow());
	return fileDialog->saveFile(title, dir, filter);
}

BFileDialog::BFileDialog(BWidget* parent) : BDialog(parent)
{
	member_allocate();
	this->setSize(800, 560);
}
BFileDialog::~BFileDialog()
{
	member_release();
}

void BFileDialog::setEditable(bool editable)
{
	if (editable && member.itemEdit.empty())
	{
		member.itemEdit = new BLineEdit();
		member.itemEdit->hide();
		member.connect(member.itemEdit, Signal_Focused, &member_BFileDialog::slotItemEditFocused);
		member.connect(member.itemEdit, Signal_EditFinished, &member_BFileDialog::slotItemEditFinished);
		this->emit(Signal_Editable, editable);
		return;
	}
	if (!editable && member.itemEdit.valid())
	{
		member.itemEdit->setAsset(BValue());
		member.itemEdit->setParent(0);
		member.itemEdit = 0;
		this->emit(Signal_Editable, editable);
		return;
	}
}
bool BFileDialog::editable() const
{
	return member.itemEdit;
}

void BFileDialog::setFolderFirst(bool folderFirst)
{
	if (member.tableWidget->folderFirst != folderFirst)
	{
		member.tableWidget->folderFirst = folderFirst;
		member.tableWidget->sortColumn();
	}
}
bool BFileDialog::folderFirst() const
{
	return member.tableWidget->folderFirst;
}

void BFileDialog::setAttribFilter(Attrib attribFilter)
{
	if (member.attribFilter != attribFilter)
	{
		member.attribFilter = attribFilter;
		bool needFilter = attribFilter & Attrib_File;
		member.filterComboBox->setVisible(needFilter);
		member.selectEntries();
	}
}
Attrib BFileDialog::attribFilter() const
{
	return member.attribFilter;
}

inline bool CheckExt(BString& ext)
{
	if (ext.empty())
		return false;
	ext.lower();
	const char* cstr = ext.cstr();
	for (int i = 0; i < ext.size(); i++)
	{
		char c = cstr[i];
		if ('a' <= c && c <= 'z')
			continue;
		if (c == '_')
			continue;
		return false;
	}
	return true;
}

void BFileDialog::setNameFilter(const BString& nameFilter)
{
	if (member.nameFilter != nameFilter)
	{
		member.nameFilter = nameFilter;
		BString ext;
		BStringArray exts;
		BStringArray words;
		int count = nameFilter.split(words, ';');
		member.filterComboBox->clearItems();
		for (int i = 0; i < count; i++)
		{
			BString& text = words[i];
			text.trimmed();
			exts.reset();
			int pos = text.find('.');
			while (pos < text.size())
			{
				text.word(ext, ++pos);
				if (CheckExt(ext))
				{
					exts.append(ext);
				}
				pos = text.find('.', pos);
			}
			member.filterComboBox->addItem(text, exts);
		}
		member.filterComboBox->setCurrentIndex(0);
		member.selectEntries();
	}
}
const BString& BFileDialog::nameFilter() const
{
	return member.nameFilter;
}

void BFileDialog::setSelectMultiple(bool selectMultiple)
{
	if (member.selectMultiple != selectMultiple)
	{
		member.selectMultiple = selectMultiple;
		if (selectMultiple)
			member.tableWidget->setSelectMode(Select_Rows | Select_Multiple);
		else
			member.tableWidget->setSelectMode(Select_Rows);
	}
}
bool BFileDialog::selectMultiple() const
{
	return member.selectMultiple;
}

void BFileDialog::setImageExts(const BString& imageExts)
{
	if (member.imageExts != imageExts)
	{
		member.imageExts = imageExts;
		member.freshTableItems();
	}
}
const BString& BFileDialog::imageExts() const
{
	return member.imageExts;
}

void BFileDialog::setPath(const BString& path)
{
	if (member.pathComboBox->text() != path)
	{
		member.openFolder(path);
		if (this->checks(Signal_PathChanged))
			this->emit(Signal_PathChanged, path);
	}
}
const BString& BFileDialog::path() const
{
	return member.pathComboBox->text();
}

void BFileDialog::setDefaultName(const BString& defaultName)
{
	if (member.defaultName != defaultName)
	{
		member.defaultName = defaultName;
		member.selectEntries();
	}
}
const BString& BFileDialog::defaultName() const
{
	return member.defaultName;
}

void BFileDialog::setDefaultSuffix(const BString& defaultSuffix)
{
	for (int i = 0; i < member.filterComboBox->itemCount(); i++)
	{
		BComboItem* item = member.filterComboBox->item(i);
		const BStringArray& exts = item->value();
		if (exts.contain(defaultSuffix))
		{
			member.filterComboBox->setCurrent(item);
			break;
		}
	}
}
const BString& BFileDialog::defaultSuffix() const
{
	return member.filterComboBox->value();
}

bool BFileDialog::addSymbol(BEntry* entry)
{
	if (!entry)
		return false;
	if (member.listWidget->itemOfAsset(entry))
	{
		return true;
	}
	BListItem* symbolItem = member.listWidget->addItem(entry->name());
	symbolItem->setIcon(member.iconOf(entry));
	symbolItem->setAsset(entry);
	member.listWidget->setVisible(true);
	return true;
}
bool BFileDialog::addSymbol(const BString& symbol)
{
	if (BEntry* entry = BEntry::Open(symbol))
	{
		BListItem* symbolItem = member.listWidget->addItem(entry->name());
		symbolItem->setIcon(member.iconOf(entry));
		symbolItem->setAsset(entry);
		member.listWidget->setVisible(true);
		return true;
	}
	return false;
}
bool BFileDialog::removeSymbol(const BString& symbol)
{
	for (int i = 0; i < member.listWidget->itemCount(); i++)
	{
		BListItem* item = member.listWidget->item(i);
		if (BDisk* disk = item->asset())
			if (disk->path() == symbol)
				return member.listWidget->removeItem(item);
		if (BEntry* entry = item->asset())
			if (entry->path() == symbol)
				return member.listWidget->removeItem(item);
	}
	return false;
}
bool BFileDialog::removeSymbol(const BEntry* entry)
{
	for (int i = 0; i < member.listWidget->itemCount(); i++)
	{
		BListItem* item = member.listWidget->item(i);
		if (item->asset() == entry)
			return member.listWidget->removeItem(item);
	}
	return false;
}

int BFileDialog::symbolCount() const
{
	return member.listWidget->itemCount();
}
const BString& BFileDialog::symbol(int index) const
{
	if (BListItem* item = member.listWidget->item(index))
	{
		if (BDisk* disk = item->asset())
		{
			return disk->name();
		}
		if (BEntry* entry = item->asset())
		{
			return entry->path();
		}
	}
	return member.pathRecords.def();
}

BEntry* BFileDialog::symbolEntry(int index)
{
	if (BListItem* item = member.listWidget->item(index))
	{
		BEntry* entry = item->asset();
		return entry;
	}
	return 0;
}
const BEntry* BFileDialog::symbolEntry(int index) const
{
	if (BListItem* item = member.listWidget->item(index))
	{
		BEntry* entry = item->asset();
		return entry;
	}
	return 0;
}

const BString& BFileDialog::fileName() const
{
	return member.fileNames.first();
}
const BStringList& BFileDialog::fileNameList() const
{
	return member.fileNames;
}

const BString& BFileDialog::openEntry(const BString& title, const BString& dir)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	this->setAttribFilter(Attrib_All);
	this->setSelectMultiple(false);
	this->execute();
	return this->fileName();
}
const BStringList& BFileDialog::openEntries(const BString& title, const BString& dir)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	this->setAttribFilter(Attrib_All);
	this->setSelectMultiple(true);
	this->execute();
	return this->fileNameList();
}

const BString& BFileDialog::openFolder(const BString& title, const BString& dir)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	this->setAttribFilter(Attrib_Folder);
	this->setSelectMultiple(false);
	this->execute();
	return this->fileName();
}
const BStringList& BFileDialog::openFolders(const BString& title, const BString& dir)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	this->setAttribFilter(Attrib_Folder);
	this->setSelectMultiple(true);
	this->execute();
	return this->fileNameList();
}

const BString& BFileDialog::openFile(const BString& title, const BString& dir, const BString& filter)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	if (filter.size())
		this->setNameFilter(filter);
	this->setAttribFilter(Attrib_File);
	this->setSelectMultiple(false);
	this->execute();
	return this->fileName();
}
const BStringList& BFileDialog::openFiles(const BString& title, const BString& dir, const BString& filter)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	if (filter.size())
		this->setNameFilter(filter);
	this->setAttribFilter(Attrib_File);
	this->setSelectMultiple(true);
	this->execute();
	return this->fileNameList();
}

const BString& BFileDialog::saveFile(const BString& title, const BString& dir, const BString& filter)
{
	this->setTitle(title);
	if (dir.size())
		this->setPath(dir);
	if (filter.size())
		this->setNameFilter(filter);
	this->setAttribFilter(Attrib_File);
	this->setSelectMultiple(false);
	this->execute();
	return this->fileName();
}

bool BFileDialog::input(const BString& text, const BValue& value)
{
	if (text == "path-combobox")
	{
		if (BComboBox* pathComboBox = value)
		{
			if (member.pathComboBox)
			{
				member.disconnect(member.pathComboBox);
				int index = member.topLayout->index(member.pathComboBox);
				member.topLayout->remove(index);
				member.topLayout->insertWidget(index, pathComboBox, 100);
			}
			member.pathComboBox = pathComboBox;
			member.connect(pathComboBox, Signal_CurrentChanged, &member_BFileDialog::slotPathCurrentChanged);
			member.connect(pathComboBox, Signal_EditFinished, &member_BFileDialog::slotPathEditFinished);
			return true;
		}
		return false;
	}
	if (text == "editable")
	{
		bool editable = value;
		this->setEditable(editable);
		return true;
	}
	if (text == "attrib-filter")
	{
		Attrib attribFilter = value;
		this->setAttribFilter(attribFilter);
		return true;
	}
	if (text == "name-filter")
	{
		const BString& nameFilter = value;
		this->setNameFilter(nameFilter);
		return true;
	}
	if (text == "image-exts")
	{
		const BString& imageExts = value;
		this->setImageExts(imageExts);
		return true;
	}
	if (text == "path")
	{
		const BString& path = value;
		this->setPath(path);
		return true;
	}
	if (text == "edit" && member.itemEdit)
	{
		if (const BTreeItem* treeItem = value)
		{
			member.itemEdit->setAsset(value);
			member.itemEdit->setPos(BCursor::GetPos());
			BRect area = treeItem->textArea();
			BSize ssize = member.itemEdit->shellSize();
			BRect rect = area + ssize;
			rect -= BPoint(ssize.width() / 2, ssize.height() / 2);
			if (rect.right() >= member.treeWidget->centerRect().right())
				rect.right() = member.treeWidget->centerRect().right() - 1;
			member.itemEdit->setParent(member.treeWidget);
			member.itemEdit->setRect(rect);
			member.itemEdit->setText(treeItem->text());
			member.itemEdit->popup();
			return true;
		}
		if (const BTableItem* tableItem = value)
		{
			member.itemEdit->setAsset(value);
			member.itemEdit->setPos(BCursor::GetPos());
			BRect area = tableItem->textArea();
			BSize ssize = member.itemEdit->shellSize();
			BRect rect = area + ssize;
			rect -= BPoint(ssize.width() / 2, ssize.height() / 2);
			if (rect.right() >= member.tableWidget->centerRect().right())
				rect.right() = member.tableWidget->centerRect().right() - 1;
			member.itemEdit->setParent(member.tableWidget);
			member.itemEdit->setRect(rect);
			member.itemEdit->setText(tableItem->text());
			member.itemEdit->popup();
			return true;
		}
	}
	return BDialog::input(text, value);
}
bool BFileDialog::query(const BString& text, BValue& value) const
{
	if (text == "path-combobox")
	{
		value = member.pathComboBox;
		return false;
	}
	if (text == "editable")
	{
		value = member.itemEdit.valid();
		return true;
	}
	if (text == "attrib-filter")
	{
		value = member.attribFilter;
		return true;
	}
	if (text == "name-filter")
	{
		BString& text = value.fill<BString>();
		text = member.nameFilter;
		return true;
	}
	if (text == "file-exts")
	{
		BStringArray& fileExts = value.fill<BStringArray>();
		fileExts = member.filterComboBox->value();
		return true;
	}
	if (text == "image-exts")
	{
		BString& text = value.fill<BString>();
		text = member.imageExts;
		return true;
	}
	if (text == "path")
	{
		BString& text = value.fill<BString>();
		text = member.pathComboBox->text();
		return true;
	}
	return BDialog::query(text, value);
}

void BFileDialog::showEvent(const BEvent& event)
{
	BDialog::showEvent(event);
	member.lineEdited = false;
	if (member.pathComboBox->text().empty())
	{
		BString path = BApplication::currentPath();
		member.openFolder(path);
	}
	else
	{
		member.freshFolder(member.pathComboBox->text());
	}
}
void BFileDialog::closeEvent(const BEvent& event)
{
	BDialog::closeEvent(event);
	member.lineEdited = false;
	member.fileNames.clear();
	if (event.value())
	{
		if ((member.attribFilter == Attrib_Folder) && member.tableWidget->selectedItem() == 0)
		{
			member.fileNames.clear();
			member.fileNames.append(member.pathComboBox->text());
		}
		else
		{
			const BStringArray& exts = member.filterComboBox->value();
			const BString& path = member.pathComboBox->text();
			const BString& text = member.nameEdit->text();
			int begin = 0;
			int end = text.find(',');
			while (begin < end)
			{
				BString& name = member.fileNames.append();
				text.substr(name, begin, end);
				if (!path.endWith('/'))
					name.prepend('/');
				name.prepend(path);
				if (member.attribFilter & Attrib_File && name.ext() == 0 && exts.size())
				{
					name << '.' << exts.first();
					while (name.replace("..", "."));
				}
				begin = end + 1;
				end = text.find(',', begin);
			}
		}
	}
}

void BFileDialog::styleEvent(const BEvent& event)
{
	BDialog::styleEvent(event);
	const BStyle* style = this->realStyle();
	member.dirtyStyle = true;
	if (style)
	{
		const BFont* font = style->font();
		if (font)
		{
			member.splitLayout->setSpacing(font->size() / 2);

			int iconSize = font->size() / 2;
			member.prevButton->setIconSize(iconSize);
			member.nextButton->setIconSize(iconSize);
			member.aboveButton->setIconSize(iconSize);

		}
	}
}
void BFileDialog::freshEvent(const BEvent& event)
{
	BDialog::freshEvent(event);
	if (member.dirtyStyle)
	{
		for (int i = 0; i < member.treeWidget->itemCount(); i++)
		{
			member.freshTreeItem(member.treeWidget->item(i));
		}
		member.freshListItems();
		member.freshTableItems();
		member.dirtyStyle = false;
	}
}
